package fi.foyt.cs.persistence.dao;

import java.lang.reflect.ParameterizedType;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.PersistenceException;
import javax.persistence.Query;

public class GenericDAO<T> {

  @SuppressWarnings("unchecked")
  public T findById(Long id) {
    EntityManager entityManager = getEntityManager();
    return (T) entityManager.find(getGenericTypeClass(), id);
  }
  
  @SuppressWarnings("unchecked")
  public List<T> listAll() {
    EntityManager entityManager = getEntityManager();
    Class<?> genericTypeClass = getGenericTypeClass();
    Query query = entityManager.createQuery("select o from " + genericTypeClass.getName() + " o");
    return query.getResultList();
  }
  
  @SuppressWarnings("unchecked")
  public List<T> listAll(int firstResult, int maxResults) {
    EntityManager entityManager = getEntityManager();
    Class<?> genericTypeClass = getGenericTypeClass();
    Query query = entityManager.createQuery("select o from " + genericTypeClass.getName() + " o");
    query.setFirstResult(firstResult);
    query.setMaxResults(maxResults);
    return query.getResultList();
  }
  
  public Integer count() {
    EntityManager entityManager = getEntityManager();
    Class<?> genericTypeClass = getGenericTypeClass();
    Query query = entityManager.createQuery("select count(o) from " + genericTypeClass.getName() + " o");
    return (Integer) query.getSingleResult();
  }
  
  public void delete(T o) {
    EntityManager entityManager = getEntityManager();

    entityManager.getTransaction().begin();
    try {
      entityManager.remove(entityManager.merge(o));
      entityManager.getTransaction().commit();
    } finally {
      if (entityManager.getTransaction().isActive())
        entityManager.getTransaction().rollback();
    }
  }
  
  private Class<?> getGenericTypeClass() {
    ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
    return (Class<?>) parameterizedType.getActualTypeArguments()[0];
  }
  
  protected T getSingleResult(Query query) {
    @SuppressWarnings("unchecked")
    List<T> list = query.getResultList();
    if (list.size() == 0)
      return null;
    if (list.size() == 1)
      return list.get(0);
    throw new PersistenceException("SingleResult query returned more than one result");
  }
  
  protected static EntityManagerFactory getEntityManagerFactory() {
    return ENTITY_MANAGER_FACTORY;
  }
  
  protected static EntityManager getEntityManager() {
    return THREAD_LOCAL.get();
  }
  
  public static void createEntityManager() {
    EntityManager entityManager = getEntityManagerFactory().createEntityManager();
    setEntityManager(entityManager);
  }
  
  public static void releaseEntityManager() {
    EntityManager entityManager = getEntityManager();
    if (entityManager.isOpen()) {
      entityManager.close();
    }

    setEntityManager(null);
  }

  private static void setEntityManager(EntityManager entityManager) {
    THREAD_LOCAL.set(entityManager);
  }
  
  private static final ThreadLocal<EntityManager> THREAD_LOCAL = new ThreadLocal<EntityManager>();
  private static final EntityManagerFactory ENTITY_MANAGER_FACTORY = Persistence.createEntityManagerFactory("transactions-optional");
}
